﻿using PlutoStudio.EntityFramework;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity.ModelConfiguration.Configuration;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace PlutoStudio
{
    public abstract class EntityBase
    {

        public abstract bool IsTransient();

        internal abstract object Key();

    }

    public abstract class EntityBase<T> : EntityBase
    {
        [Key]
        public T Id { get; set; }

        internal override object Key()
        {
            return Id;
        }

        public override bool IsTransient()
        {
            if (EqualityComparer<T>.Default.Equals(Id, default(T)))
            {
                return true;
            }

            //Workaround for EF Core since it sets int/long to min value when attaching to dbcontext
            if (typeof(T) == typeof(int))
            {
                return Convert.ToInt32(Id) <= 0;
            }

            if (typeof(T) == typeof(long))
            {
                return Convert.ToInt64(Id) <= 0;
            }

            return false;
        }

        /// <inheritdoc/>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is EntityBase<T>))
            {
                return false;
            }

            //Same instances must be considered as equal
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            //Transient objects are not considered as equal
            var other = (EntityBase<T>)obj;
            if (IsTransient() && other.IsTransient())
            {
                return false;
            }

            //Must have a IS-A relation of types or must be same type
            var typeOfThis = GetType();
            var typeOfOther = other.GetType();
            if (!typeOfThis.IsAssignableFrom(typeOfOther) && !typeOfOther.IsAssignableFrom(typeOfThis))
            {
                return false;
            }

            return Id.Equals(other.Id);
        }

        /// <inheritdoc/>
        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        /// <inheritdoc/>
        public static bool operator ==(EntityBase<T> left, EntityBase<T> right)
        {
            if (Equals(left, null))
            {
                return Equals(right, null);
            }

            return left.Equals(right);
        }

        /// <inheritdoc/>
        public static bool operator !=(EntityBase<T> left, EntityBase<T> right)
        {
            return !(left == right);
        }

        /// <inheritdoc/>
        public override string ToString()
        {
            return $"[{GetType().Name} {Id}]";
        }
    }

    public abstract class Entity<TEntity, TKey> : EntityBase<TKey>, IEntityMapper where TEntity : EntityBase<TKey>
    {

        private static Lazy<ModuleBuilder> _moduleBuilder = new Lazy<ModuleBuilder>(() =>
          {
              AssemblyName asmName = new AssemblyName();
              asmName.Name = "PlutoStudio.Repository";
              AssemblyBuilder asmBuilder =
              AppDomain.CurrentDomain.DefineDynamicAssembly
              (asmName, AssemblyBuilderAccess.RunAndSave);
              ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule
              ("PlutoStudio.Repository");

              return modBuilder;
          }, LazyThreadSafetyMode.ExecutionAndPublication);

        void IEntityMapper.RegistTo(ConfigurationRegistrar configurations)
        {
            var config = new System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<TEntity>();
            Configuration(config);
            configurations.Add(config);
        }

        protected virtual void Configuration(System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<TEntity> config)
        {

        }
    }

}
